package dk.au.cs.peterbn.processing.compilationJobs;

import dk.au.cs.peterbn.annotations.Member;
import dk.au.cs.peterbn.annotations.Members;
import dk.au.cs.peterbn.patterns.MemberRole;
import dk.au.cs.peterbn.patterns.TypeRole;
import dk.au.cs.peterbn.processing.ProcessingJob;
import dk.au.cs.peterbn.processing.ProcessingUtil;

import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import java.util.*;

/**
 * This job asserts that all members of a pattern class under compilation
 * has an ID in correspondance with a pattern annotation of their enclosing class,
 * and that the role matches up with a valid member role of that annotation.
 */
public class CheckAllMembersValidIDs implements ProcessingJob {

    private final RoundEnvironment env;
    private final ProcessingUtil util;

    public CheckAllMembersValidIDs(RoundEnvironment env, ProcessingUtil util) {
        this.env = env;
        this.util = util;
    }


    @Override
    public void process() {
        Set<Element> elementsAnnotatedWith = new HashSet<Element>();
        elementsAnnotatedWith.addAll(env.getElementsAnnotatedWith(Member.class));
        elementsAnnotatedWith.addAll(env.getElementsAnnotatedWith(Members.class));
        Set<ExecutableElement> methods = ElementFilter.methodsIn(elementsAnnotatedWith);
        for (ExecutableElement method : methods) {
            checkMethod(method);
        }

    }

    private void checkMethod(ExecutableElement method) {
        TypeElement typeElement = (TypeElement) method.getEnclosingElement();
        List<AnnotationMirror> patternAnnotations = util.getPatternAnnotationMirrors(typeElement);
        Collection<AnnotationMirror> memberAnnotations = util.getMemberAnnotations(method);
        for (AnnotationMirror memberAnnotation : memberAnnotations) {
            String memberId = util.getId(memberAnnotation);
            boolean idExists = false;
            for (AnnotationMirror patternAnnotation : patternAnnotations) {
                String patternId = util.getId(patternAnnotation);
                idExists |= memberId.equals(patternId);
            }
            if (!idExists) {
                util.printMessage(Diagnostic.Kind.ERROR, "Unknown pattern id: " + memberId, method, memberAnnotation);
            } else {
                String memberRoleName = util.getValue(memberAnnotation, Member.Fields.ROLE);
                boolean correctPattern = false;
                Queue<TypeRole> roleQueue = new LinkedList<TypeRole>();
                for (AnnotationMirror patternAnnotation : patternAnnotations) {
                    TypeRole typeRole = util.getTypeRole(typeElement, patternAnnotation);
                    if (util.getId(patternAnnotation).equals(memberId)) {
                        roleQueue.add(typeRole);
                    }
                }
                TypeRole typeRole;
                while (!correctPattern && ((typeRole = roleQueue.poll() )!= null)) {
                    MemberRole role = typeRole.getMember(memberRoleName);
                    correctPattern = correctPattern | role != null;
                    if (!correctPattern && (TypeRole.Target.ABSTRACT == typeRole.getTarget() || TypeRole.Target.ANY == typeRole.getTarget())) {
                        for (TypeRole childTypeRole : typeRole.getImplementers()) {
                            roleQueue.add(childTypeRole);
                        }

                    }
                }

                if (!correctPattern) {
                    util.printMessage(Diagnostic.Kind.ERROR, "Member role " + memberRoleName + " not present in pattern " + memberId, method, memberAnnotation);
                }
            }

        }
    }
}
